import sys,traceback,time,subprocess,threading
from pathlib import Path
from datetime import datetime

class Const:
    RS_PATH = {
        "debug": Path("target/debug/lianstar-rs.exe"),
        "release": Path("target/release/lianstar-rs.exe"),
    }
    #
    HEAD_HEARTBEAT = "HEARTBEAT"
    HEAD_READY = "READY"
    HEAD_RAII = "RAII"
    #
    HEARTBEAT_SEND_TIMEOUT = 4.5
    PIPE_READ_LIMIT = 4096

def has_timeout(last_time:datetime, timeout_secs:float) -> bool:
    return ((datetime.now() - last_time).total_seconds() > timeout_secs)

def sync_read(process:subprocess.Popen) -> str:
    line = process.stdout.readline(Const.PIPE_READ_LIMIT)
    return line.strip()

def sync_write(process:subprocess.Popen, text:str) -> None:
    process.stdin.write(f"{text}\n")
    process.stdin.flush()

def connect_wait_ready(process:subprocess.Popen) -> None:
    print("[connect_wait_ready] begin")
    msg = sync_read(process)
    units = msg.split(' ')
    if len(units) > 0:
        if units[0].upper() == Const.HEAD_READY:
            return (units[1] if (len(units) > 1) else "<!UNGIVED!>")
    raise Exception(f"process not ready: {msg}")

def connect(path:Path) -> subprocess.Popen:
    print("[connect] begin")
    process = subprocess.Popen([str(path.resolve())],
                               stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
                               bufsize=1, universal_newlines=True)
    if not isinstance(process, subprocess.Popen): raise Exception(f"[connect] process can't open: {path}")
    print("[connect] process has connected: ", path)
    engine_name = connect_wait_ready(process)
    print("[connect] engine has ready, it's name:", engine_name)
    return process

class HeartbeatThread(threading.Thread):
    def __init__(me, process:subprocess.Popen):
        threading.Thread.__init__(me)
        me.__process = process
        me.__heartbeat_send_time = datetime.now()
        me.setDaemon(True)

    def run(me):
        while (me.__process.poll() is None):
            if has_timeout(me.__heartbeat_send_time, Const.HEARTBEAT_SEND_TIMEOUT):
                me.__heartbeat_send_time = datetime.now()
                sync_write(me.__process, Const.HEAD_HEARTBEAT)
            time.sleep(1.0)

class Session(threading.Thread):
    def __init__(me, process:subprocess.Popen, on_take):
        threading.Thread.__init__(me)
        me.__process = process
        me.__on_take = on_take
        me.__next = True
        HeartbeatThread(process).start()
        me.setDaemon(True)

    def run(me) -> None:
        print("[Session.run] begin")
        while (me.__process.poll() is None) and me.__next:
            line = sync_read(me.__process).strip()
            if len(line) > 0:
                msg = line.upper()
                me.__on_take(msg)
            time.sleep(0.2)
        print("[Session.run] end")

    def stop(me):
        sync_write(me.__process, Const.HEAD_RAII)
        me.__next = False
        return me

def main(args:list):
    rs_path = Const.RS_PATH.get((args[0] if (len(args) > 0) else "debug"), Const.RS_PATH["debug"])
    def on_take(msg:str):
        if msg.find(Const.HEAD_HEARTBEAT) < 0:
            print("[on_take] msg:", msg)
    process = connect(rs_path)
    session_thread = Session(process, on_take)
    session_thread.start()
    while (process.poll() is None):
        msg_send = input()
        if not session_thread.is_alive(): break
        if len(msg_send) > 0:
            if msg_send == ".q":
                session_thread.stop()
                break
            else:
                sync_write(process, msg_send)
    session_thread.join()
    errs = process.stderr.readlines()
    print(f"[stderr", end="")
    for e in errs:
        print(f"\n  {e.rstrip()}", end="")
    print("]\n")
try:
    main(sys.argv[1:])
except:
    traceback.print_exception(*(sys.exc_info()))
